<?php
/**
 * 京东API接口
 *
 * @author: cjj
 */
class JDClient {

    public $protocol = 'oauth2';
    public $readTimeout = 10, $connectTimeout = 10;
    public $gatewayUrl ='http://103.235.242.215:8080';// 'http://gw.api.360buy.com/routerjson';
    public $format = 'json'; //json & xml
    public $checkRequest = true;
    protected $apiUrl = array();
    protected $signMethod = 'md5';
    protected $apiVersion = '2.0';
    private $_token;
    private $_urlMap = array(//认证及TOKEN获取网址列表
        'oauth2_authorize' => 'https://oauth.jd.com/oauth/authorize',
        'oauth2_token' => 'https://oauth.jd.com/oauth/token',
    );
    private $appKey = JD_APP_ID, $appSecret = JD_APP_KEY;
    private $_authorize_token_model;

    public function __construct($options = array()) {
        $token = isset($options[0]) ? $options[0] : null;
        $is_cli = isset($options[1]) ? $options[1] : false;
        $this->_authorize_token_model = isset($options[2]) ? $options[2] : null;
        if (!$token) {
            if (!$is_cli) {
                if (isset($_REQUEST['error'])) {
                    if ('invalid_client' === $_REQUEST['error']) {
                        throw new APIException(30001, null, $_REQUEST);
                    } else {
                        throw new APIException(30002, $_REQUEST['error_description'], $_REQUEST);
                    }
                }
                if (!isset($_REQUEST['code'])) {
                    $this->authorize();
                    exit();
                }
            }
        } else {
            $sessionKey = isset($token->access_token) ? $token->access_token : null;
            if (!$sessionKey) {
                throw new APIException(30003);
            }
            $this->_token = $token;
        }
    }

    /**
     * 用户认证
     */
    public function authorize() {
        $urlType = strtolower($this->protocol . '_authorize');
        $authorizeUrlMap = isset($this->_urlMap[$urlType]) ? $this->_urlMap[$urlType] : '';
        if (!$authorizeUrlMap) {
            throw new APIException(30004);
        }
        $authorizeUrl = $authorizeUrlMap;
        $authorizeUrl .= '?';
        $state = time();
        $params = array(
            "response_type" => "code",
            "client_id" => $this->appKey,
            "redirect_uri" => JD_APP_CALLBACK,
            "state" => $state
        );
        $get = array();
        foreach ($params as $key => $val) {
            $get[] = $key . '=' . urlencode($val);
        }
        $ret_url = $authorizeUrl . join('&', $get);
        header('location:' . $ret_url);
        exit();
    }

    /**
     * 获取AccessToken
     *
     * @return array|mixed
     * @throws Exception
     */
    public function getToken() {
        if ($this->_token && is_array($this->_token) && isset($this->_token['access_token'])) {
            return $this->_token;
        }
        if (!isset($_GET['code'])) {
            $this->authorize();
            return null;
        }
        $urlType = strtolower($this->protocol . '_token');
        $tokenUrl = isset($this->_urlMap[$urlType]) ? $this->_urlMap[$urlType] : '';
        if (!$tokenUrl) {
            throw new APIException(30005);
        }
        //oauth2协议认证
        $code = $_GET['code'];
        $post_fields = array(
            'grant_type' => "authorization_code",
            'client_id' => $this->appKey,
            'client_secret' => $this->appSecret,
            'code' => $code,
            'redirect_uri' => JD_APP_CALLBACK,
            'state' => $_GET['state'],
        );
        $result = $this->send($tokenUrl, $post_fields);
        $data = json_decode($result);
        if ($result && !$data) {
            $result = iconv('gb2312', 'utf-8', $result);
            $data = json_decode($result);
        }
        if (isset($data->error_description)) {
            $error = $data;
            $error_code = isset($error->code) ? $error->code : 0;
            if ($this->_authorize_token_model && $this->_token && isset($this->_token->uid) && $error_code != 15) {
                $this->_authorize_token_model->updateToken($this->_token->uid, array('is_error_stop' => 1));
            }
            throw new APIException(20001, '同步异常(' . $error->error_description . ')', $error);
        }

        return $data;
    }

    /**
     * 得到api接口数据
     *
     * @param string $method
     * @param array $paras
     *
     * @return mixed
     * @throws Exception
     */
    public function api($method, $paras = array()) {
        $access_token = isset($this->_token) && $this->_token ? $this->_token->access_token : '';
        $paramArr = array(
            'method' => $method,
            'access_token' => $access_token,
            'timestamp' => date('Y-m-d H:i:s'),
            'app_key' => $this->appKey,
            'v' => '2.0',
        );
        if ($paras) {
            $paramArr['360buy_param_json'] = json_encode($paras);
        }
        $paramArr['sign'] = $this->createSign($paramArr, $this->appSecret);
        $strParam = $this->createStrParam($paramArr);
        $urlStr = $this->gatewayUrl . '?' . $strParam;
        $content = $this->send($urlStr);
       
         // 解析返回结果
        $respWellFormed = false;
        $result = self::jsonDecode($content,true);
        if (null !== $result) {
            $respWellFormed = true;
            foreach ($result as $propKey => $propValue) {
                $result = $propValue;
            }
        }
        if(isset($result['code']) && $result['code'] != 0){
                if((int)$result['code'] > 100){
                    echo "jump code.".$result['code']."\n";
                    return array();
                }else{
                    throw new JosApiException("jd code:".$result['code']."->".$content);
                }
        }
        if (false === $respWellFormed) {
             throw new JosApiException("can not json_decode content!follow:\n".$content);
        }
        if (null !== $result) {
            foreach ($result as $propKey => $propValue) {
                $result = $propValue;
            }
        }
        return $result;
    }

    /**
     * 京东参数签名
     *
     * @param $params
     *
     * @return string
     */
    private function createSign($params) {
        ksort($params);
        $sign = $this->appSecret;
        foreach ($params as $k => $v) {
            if ("@" != substr($v, 0, 1)) {
                $sign .= "$k$v";
            }
        }
        unset($k, $v);
        $sign .= $this->appSecret;
        return strtoupper(md5($sign));
    }

    /**
     * 京东参数组合
     *
     * @param $paramArr
     *
     * @return string
     */
    private function createStrParam($paramArr) {
        $strParam = '';
        foreach ($paramArr as $key => $val) {
            if ($key != '' && $val != '') {
                $strParam .= $key . '=' . urlencode($val) . '&'; //(mb_convert_encoding( ,"GBK"
            }
        }
        return $strParam;
    }

    /**
     * 发送请求
     *
     * @param      $url
     * @param null $postFields
     *
     * @return mixed
     * @throws Exception
     */
    public function send($url, $postFields = null) {
        if ($this->protocol == 'top') {
            return file_get_contents($url);
        }
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_FAILONERROR, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        if ($this->readTimeout) {
            curl_setopt($ch, CURLOPT_TIMEOUT, $this->readTimeout);
        }
        if ($this->connectTimeout) {
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
        }
        //https 请求
        if (strlen($url) > 5 && strtolower(substr($url, 0, 5)) == "https") {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_SSLVERSION, 3);
        }

        if (is_array($postFields) && 0 < count($postFields)) {
            $postBodyString = "";
            $postMultipart = false;
            foreach ($postFields as $k => $v) {
                if ("@" != substr($v, 0, 1)) {//判断是不是文件上传
                    $postBodyString .= "$k=" . urlencode($v) . "&";
                } else {//文件上传用multipart/form-data，否则用www-form-urlencoded
                    $postMultipart = true;
                }
            }
            unset($k, $v);
            curl_setopt($ch, CURLOPT_POST, true);
            if ($postMultipart) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
            } else {
                curl_setopt($ch, CURLOPT_POSTFIELDS, substr($postBodyString, 0, -1));
            }
        }
        $response = curl_exec($ch);
        if (curl_errno($ch)) {
            throw new APIException(1001, "API调用异常\n" . $url . "\n" . curl_error($ch));
        } else {
            $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if (200 !== $httpStatusCode) {
                throw new APIException($httpStatusCode, $response);
            }
        }
        curl_close($ch);
        return $response;
    }

    /**
     * 京东刷新授权令牌
     *
     * @param $paramArr
     *
     * @return string
     */
    function refreshToken() {
        $Url = $this->_urlMap['oauth2_token'] . "?";
        $refresh['client_id'] = $this->appKey;
        $refresh['client_secret'] = $this->appSecret;
        $refresh['grant_type'] = 'refresh_token';
        $refresh['refresh_token'] = $this->_token->refresh_token;
        $str = $this->send($Url . http_build_query($refresh));
        file_put_contents(APPPATH . "logs/refreshTokent.log", $str);
        if ($str) {
            $str = substr($str,strpos($str,'"expires_in":')+13); 
            return substr($str,0,strpos($str,',')); 
        }
        return 0;
    }

    private static function jsonDecode($str,$array = false)
    {
        // 特殊字符处理
        $str = str_replace(array(
            chr(0),
            chr(4),
            chr(15),
            chr(16),
            chr(19),
            chr(31),
            '\\v'
        ), '\\t', $str);
        return PhplutilsJSON::decode($str,$array);
    }
}
